package com.atguigu.seckill.service.impl;

import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.atguigu.common.to.mq.SecKillSkuInfoTo;
import com.atguigu.common.utils.R;
import com.atguigu.common.vo.MemberEntityVo;
import com.atguigu.seckill.cons.SecKillReidsConst;
import com.atguigu.seckill.feign.ProductFeignService;
import com.atguigu.seckill.interceptor.MySecKillInterceptor;
import com.atguigu.seckill.service.SecKillSevice;
import com.atguigu.seckill.to.SkuInfoTo;
import com.atguigu.seckill.to.seckillSessionTo;
import com.atguigu.seckill.vo.SecKillSkuReids;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

@Service
@Slf4j
public class SecKillServiceImpl implements SecKillSevice {

    @Autowired
    StringRedisTemplate redisTemplate;


    @Autowired
    ProductFeignService productFeignService;

    @Autowired
    RedissonClient redissonClient;


    @Autowired
    MySecKillInterceptor interceptor;


    @Autowired
    RabbitTemplate rabbitTemplate;

    /**
     * 秒杀的活动场次在redis中的存储使用的结构为list，key为活动的开始+结束时间  value为sku的编号
     *
     * @param sessionToList
     */
    @Override
    public void saveSecKillAc(List<seckillSessionTo> sessionToList) {

        sessionToList.stream().forEach(sessionTo -> {
            String start = sessionTo.getStartTime().getTime() + "";
            String end = sessionTo.getEndTime().getTime() + "";
            //活动在redis中存储使用的key
            String key = SecKillReidsConst.AC_PREFIX + start + "_" + end;
            List<String> skuIdList = sessionTo.getSkuRelationEntities().stream().map(skuItem -> {
                //活动的场次编号+商品的sku编号
                String skuId = sessionTo.getId() + "_" + skuItem.getSkuId().toString();
                return skuId;
            }).collect(Collectors.toList());
            //转换为string类型
            String value = JSONObject.toJSONString(skuIdList);
            //在redis中存储之前先判断是否已经存储过了
            if (!redisTemplate.hasKey(key)) {
                redisTemplate.opsForList().leftPush(key, value);
            }
        });
    }

    @Override
    public void saveSecKillProd(List<seckillSessionTo> sessionToList) {
        BoundHashOperations<String, Object, Object> operations = redisTemplate.boundHashOps(SecKillReidsConst.PROD_PREFIX);

        sessionToList.stream().forEach(sessionTo -> {
            //redis中存储的key
//            String key = sessionTo.getId().toString();
            //存储的value
            SecKillSkuReids secKillSkuReids = new SecKillSkuReids();
            //便利参与活动的每一个商品
            sessionTo.getSkuRelationEntities().forEach(item -> {
                String key = sessionTo.getId() + "_" + item.getSkuId().toString();

                if (!operations.hasKey(key)) {
                    //查询sku的基本信息
                    R skuInfo = productFeignService.getSkuInfo(item.getSkuId());
                    //远程服务调用成功
                    if (skuInfo.getCode() == 0) {
                        SkuInfoTo skuInfoData = skuInfo.getData(new TypeReference<SkuInfoTo>() {
                        });
                        secKillSkuReids.setSkuInfoTo(skuInfoData);
                    } else {
                        throw new RuntimeException("调用商品服务查询商品的sku信息失败");
                    }

                    //设置商品秒杀的开始时间和结束时间
                    secKillSkuReids.setStartTime(sessionTo.getStartTime().getTime());
                    secKillSkuReids.setEndTime(sessionTo.getEndTime().getTime());

                    //设置随机码
                    secKillSkuReids.setToken(UUID.randomUUID().toString().replace("-", ""));
                    //设置秒杀的分布式信号量
                    RSemaphore semaphore = redissonClient.getSemaphore(SecKillReidsConst.REDISSION_PREFIX + item.getSkuId());
                    //设置参与秒杀的信号量为sku的库存
                    semaphore.trySetPermits(item.getSeckillCount().intValue());


                    BeanUtils.copyProperties(item, secKillSkuReids);


                    String value = JSONObject.toJSONString(secKillSkuReids);
                    //存储
                    operations.put(key, value);
                }


            });

//            sessionTo.getSkuRelationEntities().stream().map(item->{
//                //属性对拷
//                BeanUtils.copyProperties(item,secKillSkuReids);
//
//            })

        });
    }

    //查询当前时间段正在秒杀的商品列表
    @SentinelResource(value = "method-listSecKillProducts",blockHandler ="listSecKillHandler" )
    @Override
    public List<SecKillSkuReids> listSecKillProducts() {
        try (Entry entry = SphU.entry("listSecKillProducts")) {
            List<SecKillSkuReids> secKillSkuReidsList = new ArrayList<>();
            //获取当前时间的毫秒值
            Long now = System.currentTimeMillis();
            //获取redis中存储的秒杀时间

            Set<String> keys = redisTemplate.keys(SecKillReidsConst.AC_PREFIX + "*");
//        gulimall:ac:kill:Fri Jun 03 09:50:38 CST 2022_Sat Jun 04 09:50:43 CST 2022
            //        gulimall:ac:kill:102221122233 2211000022


            for (String key : keys) {
                String replace = key.replace(SecKillReidsConst.AC_PREFIX, "");
                String[] split = replace.split("_");
                //截取开始时间和结束时间
                Long start = Long.parseLong(split[0]);
                Long end = Long.parseLong(split[1]);
                if (now >= start && now <= end) {
                    //满足条件的秒杀场次
                    //获取满足条件的商品编号
                    List<String> res = redisTemplate.opsForList().range(key, 0, -1);
                    ArrayList<String> skuIds = new ArrayList<>();
                    res.stream().forEach(item -> {
                        String[] skus = item.split(",");
                        for (String s : skus) {
//                        return s;
                            String replace1 = s.replace("\"", "").replace("[", "").replace("]", "");
//                        String replace2 = replace1.replace("[", "");
                            skuIds.add(replace1);
                        }
                    });
                    //根据获取出来的商品编号去查询商品的详细信息
//                gulimall:prod:kill
                    //操作hash结构的操作器
                    BoundHashOperations<String, String, String> operations = redisTemplate.boundHashOps(SecKillReidsConst.PROD_PREFIX);
//                //批量获取value
                    List<String> skuDetails = operations.multiGet(skuIds);
//                //转换数据结构
                    if (skuDetails.size() > 0 && skuDetails != null) {
                        secKillSkuReidsList = skuDetails.stream().map(item -> {
                            SecKillSkuReids skuReids = JSONObject.parseObject(item, SecKillSkuReids.class);
                            return skuReids;
                        }).collect(Collectors.toList());
                    }
                }

            }
            return secKillSkuReidsList;
        } catch (BlockException exception) {
            log.error("资源被限流。。。", exception.getMessage());
        }

        return null;

    }


    public List<SecKillSkuReids> listSecKillHandler(BlockException flowException) {
        log.error("listSecKillProducts的限流方法执行");
        log.info("flowException:"+flowException.getMessage());
        return null;
    }







    @Override
    public SecKillSkuReids querySkuSeckillInfo(String skuId) {
        //查询当前商品参与秒杀活动的详细信息
        BoundHashOperations<String, String, String> operations = redisTemplate.boundHashOps(SecKillReidsConst.PROD_PREFIX);
        //获取所有的key
        Set<String> keys = operations.keys();
        //1_49。使用正则进行匹配过滤
//        String reg="/d/"+skuId;
        String reg = "\\d_" + skuId;
        for (String key : keys) {
            if (Pattern.matches(reg, key)) {
                //当前商品编号匹配上了
                String res = operations.get(key);
                SecKillSkuReids skuReids = JSONObject.parseObject(res, SecKillSkuReids.class);
                //判断该商品的查询发起的时间是不是属于商品秒杀的时间范围内。如果在范围内，那么将商品的随机码也返回给前端。如果不是的话，将当前商品的随机码设置为null返回出去
                long now = new Date().getTime();
                Long startTime = skuReids.getStartTime();
                Long endTime = skuReids.getEndTime();
                if (startTime <= now && now <= endTime) {
                    return skuReids;
                } else {
                    skuReids.setToken(null);
                    return skuReids;
                }
            }
        }
        return null;
    }


    /**
     * 处理秒杀请求
     *
     * @param killId 秒杀商品的编号
     * @param key    秒杀商品的随机码
     * @param num    秒杀的个数
     * @return
     */
    @Override
    public String dealSecKill(String killId, String key, Integer num) throws InterruptedException {
        //先根据传入的killId查询当前的秒杀商品
        BoundHashOperations<String, String, String> operation = redisTemplate.boundHashOps(SecKillReidsConst.PROD_PREFIX);
        String res = operation.get(killId);
        //转换对象
        SecKillSkuReids secKillSkuReids = JSONObject.parseObject(res, SecKillSkuReids.class);
        //合法性的校验(秒杀时间、商品随机码、幂等性)
        Long now = new Date().getTime();
        Long startTime = secKillSkuReids.getStartTime();
        Long endTime = secKillSkuReids.getEndTime();
        //过期时间
        long ttl = endTime - now;

        //处于秒杀时间的范围内
        if (now >= startTime && now <= endTime) {

            //校验库存是否充足
            int secKillCount = Integer.parseInt(redisTemplate.opsForValue().get(SecKillReidsConst.REDISSION_PREFIX + secKillSkuReids.getSkuId()));
            int skuStock = secKillSkuReids.getSeckillCount().intValue();
            if (skuStock <= 0 || num >= secKillCount || secKillCount <= 0) {
                return null;
            }

            //秒杀商品随机码校验
            String token = secKillSkuReids.getToken();
            if (key.equals(token)) {
                //幂等性处理(用来判断用户是否已经秒杀过了)
                ValueOperations<String, String> opsForValue = redisTemplate.opsForValue();
                //key: userId+"_"+killId+"_"+num
                MemberEntityVo memberEntityVo = MySecKillInterceptor.threadLocal.get();
                Long id = memberEntityVo.getId();
                //setIdAbsent==setNx():没有的时候进行设置
                Boolean flag = opsForValue.setIfAbsent(id.toString() + "_" + killId, num.toString(), ttl, TimeUnit.MILLISECONDS);
                if (flag) {
                    //获取信号量，继续进行秒杀
                    RSemaphore semaphore = redissonClient.getSemaphore(SecKillReidsConst.REDISSION_PREFIX + secKillSkuReids.getSkuId());
                    boolean tryAcquire = semaphore.tryAcquire(num, 100, TimeUnit.MILLISECONDS);
                    if (tryAcquire) {
                        //获取成功，即抢购成功，可以进行下一步，向消息队列去发送消息，进行后续的处理.(使用队列进行削峰处理)
                        String timeId = IdWorker.getTimeId();
                        //创建出快速订单
                        SecKillSkuInfoTo skuInfoTo = new SecKillSkuInfoTo();
                        skuInfoTo.setUserId(id.toString());
                        skuInfoTo.setCount(num);
                        skuInfoTo.setPromotionSessionId(secKillSkuReids.getPromotionSessionId());
                        skuInfoTo.setQuickOrderSn(timeId);
                        skuInfoTo.setSkuDesc(secKillSkuReids.getSkuDesc());
                        skuInfoTo.setSkuName(secKillSkuReids.getSkuName());
                        skuInfoTo.setSeckillPrice(secKillSkuReids.getSeckillPrice());
                        skuInfoTo.setSkuId(secKillSkuReids.getSkuId());
                        //将消息发送给消息队列
                        rabbitTemplate.convertAndSend("order-event-exchange", "order.seckill.order", skuInfoTo);

                        //返回快速订单号
                        return timeId;


                    }
                }
            }

        }


        return null;
    }
}
